{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "</center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Индивидуальный проект. Прогноз успеваемости школьников\n",
    "<center> Автор: Куценко Андрей (@anti111)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "%matplotlib inline\n",
    "\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "from sklearn.metrics import mean_squared_error\n",
    "\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.model_selection import GridSearchCV\n",
    "\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from sklearn.neighbors import KNeighborsRegressor\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.ensemble import GradientBoostingRegressor\n",
    "from sklearn.ensemble import AdaBoostRegressor\n",
    "from sklearn.svm import LinearSVR\n",
    "\n",
    "from sklearn.learning_curve import learning_curve\n",
    "\n",
    "from scipy import stats"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**[Данные](https://www.kaggle.com/uciml/student-alcohol-consumption/data) по успеваемости школьников (средняя школа)** "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Нас будет интересовать только успеваемость по математике\n",
    "#data = pd.read_csv('student-mat.csv')\n",
    "data = pd.read_csv('../../data/student-mat.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Количество учащихся:\",len(data))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Первый взгляд на данные\n",
    "data.head().T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#В данных пропусков нет, повезло.\n",
    "print(len(data)-len(data.dropna()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.get_ftype_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Признаки #"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Всего три типа признаков **binary** - бинарный, **numeric** - численный и **nominal** - категориальный.\n",
    "\n",
    "**school** - школа студента (binary: 'GP' - Gabriel Pereira или 'MS' - Mousinho da Silveira)   \n",
    "**sex** - пол студента (binary: 'F' - female или 'M' - male)   \n",
    "**age** - возраст студента (numeric: от 15 до 22)   \n",
    "**address** - тип места проживания (binary: 'U' - город or 'R' - сельская местность)   \n",
    "**famsize** - размер семьи (binary: 'LE3' - меньше или равно 3 or 'GT3' - больше 3)   \n",
    "**Pstatus** - живут ли родители совместно (binary: 'T' - живут вместе или 'A' - поотдельности)   \n",
    "**Medu** - образование матери (numeric: 0 - отсутствует, 1 - начальная школа (4th grade), 2 – с 5-ого по 9ый класс, 3 – оконченная средняя школа or 4 – высшее образование)    \n",
    "**Fedu** - образование отца (numeric: 0 - отсутствует, 1 - начальная школа (4th grade), 2 – с 5-ого по 9ый класс, 3 – оконченная средняя школа or 4 – высшее образование)       \n",
    "**Mjob** - работа матери (nominal: 'teacher'-учитель, 'health'-здравоохранения, 'services' - гражданская\\государственная работа (например административная или в полиции), 'at_home' - дома или 'other')     \n",
    "**Fjob** - работа отца (nominal: 'teacher'-учитель, 'health'-здравоохранения, 'services' - гражданская\\государственная работа (например административная или в полиции), 'at_home' - дома или 'other')    \n",
    "**reason** - причина выбора школы (nominal:'home'-близко к дому, 'reputation'-репутация школы, 'course'-предпочтения в курсах или 'other')        \n",
    "**guardian** - представитель студента (nominal: 'mother', 'father' или 'other')   \n",
    "**traveltime** - время от дома до школы (numeric: 1 - меньше 15 мин., 2 - от 15 до 30 мин., 3 - от 30 мин. до 1 часа, или 4 - больше 1 часа)   \n",
    "**studytime** - еженедельные временные затраты на обучение (numeric: 1 - меньше 2 часов, 2 - от 2 до 5 часов, 3 - от 5 to 10 hours, или 4 - больше 10 часов)   \n",
    "**failures** - количество проваленных классов в прошлом (numeric: n если n=1,2,3, иначе 4)   \n",
    "**schoolsup** - дополнительная поддержа в обучении (видимо финансовая) (binary: yes or no)   \n",
    "**famsup** - семейная образовательная поддержка (binary: yes or no)   \n",
    "**paid** - оплата дополнительных уроков по предмету (Math) (binary: yes or no)    \n",
    "**activities** - внеклассовые активности (binary: yes or no)    \n",
    "**nursery** - учился ли в детском саду (binary: yes or no)    \n",
    "**higher** - желание получить высшее образование (binary: yes or no)     \n",
    "**internet** - домашний доступ к интернету (binary: yes or no)     \n",
    "**romantic** - в любовных отношениях (binary: yes or no)     \n",
    "**famrel** - качество семейных взаимоотношений (numeric: от 1 - очень плохие до 5 - отличные)     \n",
    "**freetime** - кол-во свободного времени после школы (numeric: от 1 - очень мало до 5 - очень много)     \n",
    "**goout** - как часто гуляет с друзьями (numeric: от 1 - очень мало до 5 - очень много)     \n",
    "**Dalc** - кол-во потребляемого алкоголя в будни (numeric: от 1 - очень мало до 5 - очень много)     \n",
    "**Walc** - кол-во потребляемого алкоголя в выходные (numeric: от 1 - очень мало до 5 - очень много)     \n",
    "**health** - состояние здоровья (numeric: от 1 - очень плохое до 5 - отличное)      \n",
    "**absences** - количество пропусков занятий (numeric: от 0 до 93)     \n",
    "\n",
    "Семестровые оценки по математике (целевые признаки):\n",
    "\n",
    "**G1** - оценка за первый семестр (numeric: от 0 до 20)     \n",
    "**G2** - оценка за второй семестр (numeric: от 0 до 20)    \n",
    "**G3** - финальная оценка (numeric: от 0 до 20)    \n",
    "\n",
    "**Всего признаков:33**   \n",
    "**Количество учащихся:395**\n",
    "\n",
    "**Пожалуй, интересней всего в этой задаче посмотреть взаимодействия между признаками и выделить признаки наиболее влияющие на успеваемость. Научится предсказывать оценку тоже не плохо, например, зная оценки в одном классе школы можно предсказать оценки в другом классе.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сразу преобразуем категориальные и бинарные признаки к булевому типу, чтобы матрицу корреляции можно было построить для всех признаков ###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Разбиение признаков на группы для дальнейшего преобразования ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "binary_features=['school','sex','address','famsize','Pstatus','schoolsup','famsup','paid','activities','nursery','higher','internet','romantic']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nominal_features=['Mjob','Fjob','reason','guardian']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numeric_features=['age','Medu','Fedu','traveltime','studytime','failures','famrel','freetime','goout','Dalc','Walc','health','absences']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "target_features=['G1','G2','G3']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(binary_features)+len(nominal_features)+len(numeric_features)+len(target_features)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_num=data[numeric_features]\n",
    "data_bin=data[binary_features]\n",
    "data_nom=data[nominal_features]\n",
    "data_target=data[target_features]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Преобразование бинарных и категориальных признаков методами pandas (one hot encoding) и отсечение признаков не добавляющих информации###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_bin_dum=pd.get_dummies(data_bin)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Бинарные признаки разбились на взаимодополняющие пары, из каждой пары достаточно оставить только один признак.\n",
    "data_bin_cut=data_bin_dum.iloc[:,1::2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_nom_dum=pd.get_dummies(data_nom)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Зная другие признаки соответствующие исходному категориальному признаку, значения этих признаков определяется однозначно.\n",
    "#Удаляем\n",
    "data_nom_cut=data_nom_dum.drop(['Mjob_other','Fjob_other','reason_other','guardian_other'],axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Собираем всё вместе в новый датафрейм ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reformed=pd.concat([data_num,data_bin_cut,data_nom_cut,data_target],axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Создание целевой переменной и статистика ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Целевая переменная, которую мы будем предсказывать - это средняя сумма баллов за все экзамены.\n",
    "data_reformed['G_average']=(data_reformed['G1']+data_reformed['G2']+data_reformed['G3'])/3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Посмотрим на статистику\n",
    "data_reformed.describe().T\n",
    "#В бинарных признаках например: higher_yes 1 - означает да(хочет получить высшее образование), 0 - означает нет."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Построим матрицу корреляций ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.set(font_scale=2.5)\n",
    "plt.figure(figsize=(40,30))\n",
    "corr_matrix=data_reformed.corr()\n",
    "sns.heatmap(corr_matrix,annot=True,fmt = \".2f\",cbar = True,cmap='PuOr',annot_kws={\"size\":18})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Что можно заметить? ###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Видно, что оценка за экзамен больше всего коррелирует с оценкой за другие экзамены, так что выбранная целевая переменная (среднее значение за все экзамены) вполне отражает то, что хочется спрогнозировать. Видим также сильную корреляцию между признаками созданными из одной категориальной переменной - это не очень интересно и большую пользу из этого врядли удастся извлечь ####\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Присутствует также сильная корреляция между уровнем образования матери и отца\n",
    "print(corr_matrix['Fedu']['Medu'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Есть и вполне естественные корреляции между потреблением алкоголя по будням, потреблением алкоголя в выходные и тем\n",
    "#сколько человек гуляет с друзьями.\n",
    "print(corr_matrix['Dalc']['Walc'])\n",
    "print(corr_matrix['Walc']['goout'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Очень интересно, большая отрицательная корреляция между мужским полом и временем затрат на учебу\n",
    "print(corr_matrix['sex_M']['studytime'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# А также вполне логичная корреляция между проживанием в городе (а не сельской местности) и временем на дорогу до школы\n",
    "print(corr_matrix['address_U']['traveltime'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Посмотрим на корелляцию признаков с целевой переменной (последний столбец) ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Бросается в глаза сильная корелляция между количеством не сдач экзамена ранее и средней оценкой\n",
    "print(corr_matrix['G_average']['failures'])\n",
    "#В некотором смысле failures это обратный признак к средней оценке, пожалуй, для чистоты эксперимента его надо исключить\n",
    "data_reformed=data_reformed.drop(['failures'],axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Сильная положительная корреляция оценки с уровнем образования матери и отца, временем занятий и желанием получить \n",
    "#высшее образование.\n",
    "print(corr_matrix['G_average']['Medu'])\n",
    "print(corr_matrix['G_average']['Fedu'])\n",
    "print(corr_matrix['G_average']['studytime'])\n",
    "print(corr_matrix['G_average']['higher_yes'])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#А также оценка по математике положительно коррелирует с мужским полом.\n",
    "print(corr_matrix['sex_M']['G_average'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Из отрицательных корреляций наиболее выделяются тусовки с друзьями, schoolsup и работа матери на дому, ого!\n",
    "print(corr_matrix['G_average']['goout'])\n",
    "print(corr_matrix['G_average']['schoolsup_yes'])\n",
    "print(corr_matrix['G_average']['Mjob_at_home'])\n",
    "#Есть подозрение, что schoolsup - это поддержка бедных детей (а бедные дети скорее всего хуже учатся)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Посмотрим на распределение наиболее интересных признаков (по остальным можно судить по статистике построенной ранее) ##"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reformed.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "features_to_hist=['age','Medu','Fedu','studytime']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Строим графики\n",
    "sns.set(font_scale=1.2)\n",
    "data_reformed[features_to_hist].hist(figsize=(12,10),bins=8)\n",
    "#sns.pairplot(data_reformed[features_to_hist + ['G_average']])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Много родителей не закончили среднюю школу, гм!\n",
    "#Большинство учеников тратят на учебу от 2 до 5 часов в неделю\n",
    "#А средний возраст равен:\n",
    "print('средний возраст:',data_reformed['age'].mean())\n",
    "#Людей старше 19 всего лишь 5, максимальный возраст - 22 года.\n",
    "data_reformed['age'].value_counts()\n",
    "#Существенных выбросов в данных тоже не обнаружено"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Наконец посмотрим на распределение целевой переменной ##"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reformed['G_average'].hist(bins=25)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Немного напоминает нормальное распределение\n",
    "# Посмотрим, что говорят статистические тесты.\n",
    "print(stats.normaltest(data_reformed['G_average']))\n",
    "print('skew=',stats.skew(data_reformed['G_average']))\n",
    "print(stats.skewtest(data_reformed['G_average']))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Выбор метрики ###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Так как мы решаем задачу регрессии и значимых выбросов в задаче нет, то можно использовать метрику MSE. Впринципе, можно было бы использовать и R2 метрику (коэффициент детерминации), но, вооружившись baseline в виде начального приближения средним, будем использовать MSE. ####"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Отделение признаков от целевой переменной\n",
    "X=data_reformed.iloc[:,:-4]\n",
    "y=data_reformed.iloc[:,-1:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Разбиение выборки на train и test, первый 'baseline'###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Заодно сразу сделаем разбиение на train и test.\n",
    "#Обязательно перемешать!\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30,shuffle=True,random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Среднее значение оценки на трейне\n",
    "y_mean=y_train.mean()\n",
    "y_mean_for_test=[float(y_mean) for x in range(len(y_test))]\n",
    "y_mean_for_train=[float(y_mean) for x in range(len(y_train))]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Результаты приближения средним на трейне\n",
    "print(mean_squared_error(y_train,y_mean_for_train))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Результаты приближения средним на тесте\n",
    "baseline_mean=mean_squared_error(y_test,y_mean_for_test)\n",
    "print(mean_squared_error(y_test,y_mean_for_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Выбор модели #"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Для данной задачи хорошо подходит случайный лес, так как в задаче приличное количество признаков разных типов, а с этим случайный лес справляется хорошо, также он не чувствителен к масштабированию и может помочь отобрать наиболее важные признаки. Но видно, что целевая переменная зависит от многих признаков линейно, так что может пригодиться и простая линейная регрессия. ###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Впрочем, ничто не мешает опробовать и другие регрессоры ###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Для начала закончим предобработку данных и отберем признаки с помощью случайного леса####"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Для удобства сделаем массивы целевых переменных строками\n",
    "y_train_row=[]\n",
    "y_test_row=[]\n",
    "for x in y_train.iloc[:,0]:\n",
    "    y_train_row.append(x)\n",
    "for x in y_test.iloc[:,0]:\n",
    "    y_test_row.append(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Обучаем базовый случайны лес для отбора признаков\n",
    "rfc_base=RandomForestRegressor(n_estimators=500,random_state=42)\n",
    "rfc_base.fit(X_train,y_train_row)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Выводим значимость признаков\n",
    "features = pd.DataFrame(rfc_base.feature_importances_, index=X_train.columns,\n",
    "                        columns=['Importance']).sort_values(['Importance'], ascending=False)\n",
    "features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Отбросим 15 самых малозначимых признаков\n",
    "#Я пробовал удалять разное количество малозначимых признаков и удалить 15 оказалось оптимальным.(с точки зрения кроссвалидации, см. далее)\n",
    "features_cutted=features.iloc[:-15].index\n",
    "features_cutted"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Оставим в трейне и тесте только эти признаки\n",
    "X_train_cutted=X_train[features_cutted]\n",
    "X_test_cutted=X_test[features_cutted]\n",
    "#На этом преобработка данных полностью закончена"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Приступим к выбору модели\n",
    "#Список регрессоров\n",
    "regressors = [LinearRegression(),\n",
    "               GradientBoostingRegressor(random_state=42), \n",
    "               RandomForestRegressor(random_state=42), \n",
    "               LinearSVR(random_state=42)]\n",
    "regressor_name = ['LinearRegression',\n",
    "                    'GradientBoostingRegressor', \n",
    "                    'RandomForestRegressor', \n",
    "                    'LinearSVR']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Параметры к регрессорам\n",
    "scores = []\n",
    "fits = []\n",
    "linear_params = {'normalize': (True, False)}\n",
    "gbr_params = {'n_estimators': [100, 300, 500],\n",
    "              'learning_rate':(0.1, 0.5, 1),\n",
    "              'max_depth': list(range(3, 10, 2)), \n",
    "              'min_samples_leaf': list(range(10, 31, 10))}\n",
    "forest_params = {'n_estimators': [100, 300, 500], \n",
    "                 'max_depth': list(range(3, 10, 2)), \n",
    "                 'min_samples_leaf': list(range(10, 31, 10))}\n",
    "\n",
    "svm_params = {'loss' : ('epsilon_insensitive', 'squared_epsilon_insensitive'), 'C': (.5, 1, 2)}\n",
    "params = [linear_params, gbr_params, forest_params, svm_params]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Перебираем параметры регрессоров в поисках лучшего (на 5 фолдах)\n",
    "np.random.seed(42)\n",
    "for i, each_regressor in enumerate(regressors):\n",
    "    reg = each_regressor\n",
    "    reg_params = params[i]\n",
    "    grid = GridSearchCV(reg, reg_params, \n",
    "                        cv=5,\n",
    "                        scoring='neg_mean_squared_error',\n",
    "                        n_jobs=-1)\n",
    "    grid.fit(X_train_cutted, y_train_row)\n",
    "    fits.append(grid.best_params_)\n",
    "    reg_best_score = grid.best_score_\n",
    "    scores.append(reg_best_score)\n",
    "    print(regressor_name[i], -reg_best_score, \"\\n\", grid.best_params_, \"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### А вот результаты которые бы получились, если не отбирать признаки случайным лесом.\n",
    "LinearRegression 14.0748985397 \n",
    " {'normalize': False} \n",
    "\n",
    "GradientBoostingRegressor 11.7584062474 \n",
    " {'learning_rate': 0.1, 'max_depth': 3, 'min_samples_leaf': 20, 'n_estimators': 100} \n",
    "\n",
    "RandomForestRegressor 11.3097947653 \n",
    " {'max_depth': 9, 'min_samples_leaf': 10, 'n_estimators': 100} \n",
    "\n",
    "LinearSVR 12.8721586628 \n",
    " {'C': 2, 'loss': 'epsilon_insensitive'} \n",
    " \n",
    "### Отбор признаков действительно помог улучшить результат ###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Неплохо себя показал градиентный бустинг, но все же случайный лес оказался лучше, его и будем дальше использовать. ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Углубленный подбор гиперпараметров для случайного леса\n",
    "np.random.seed(42)\n",
    "forest_params_deep = {'n_estimators': [100,150,200,300,500], #n_estimators - количество деревьев в случайном лесе\n",
    "                 'max_depth': list(range(3, 13, 2)), #max_depth - максимальная глубина дерева\n",
    "                 'min_samples_leaf': list(range(5, 30, 5))}#min_samples_leaf - минимальное количество объектов в листе дерева.\n",
    "rfr=RandomForestRegressor(random_state=42)\n",
    "grid_rfr = GridSearchCV(rfr, forest_params_deep, \n",
    "                        cv=5,\n",
    "                        scoring='neg_mean_squared_error',\n",
    "                        n_jobs=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_rfr.fit(X_train_cutted, y_train_row)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Результат стал еще лучше!\n",
    "print(-grid_rfr.best_score_,'\\n',grid_rfr.best_params_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Еще раз взглянем на важность признаков и держа в голове ранее исследованную матрицу корреляции, построим новые признаки.\n",
    "features = pd.DataFrame(grid_rfr.best_estimator_.feature_importances_, index=X_train_cutted.columns,\n",
    "                        columns=['Importance']).sort_values(['Importance'], ascending=False)\n",
    "features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Построение новых признаков ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_new_features=X_train_cutted.copy()\n",
    "X_test_new_features=X_test_cutted.copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Вспомним про сильно коррелированные признаки потребления алкоголя в будни и выходные, пожалуй их стоит объединить в один.\n",
    "X_train_new_features['Talc']=X_train_new_features['Dalc']+X_train_new_features['Walc']\n",
    "X_test_new_features['Talc']=X_test_new_features['Dalc']+X_test_new_features['Walc']\n",
    "\n",
    "X_train_new_features=X_train_new_features.drop(['Dalc','Walc'],axis=1)\n",
    "X_test_new_features=X_test_new_features.drop(['Dalc','Walc'],axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Образование матери и отца тоже сильно коррелирует, пожалуй стоит учитывать только суммарное образование родителей.\n",
    "X_train_new_features['Pedu']=X_train_new_features['Medu']+X_train_new_features['Fedu']\n",
    "X_test_new_features['Pedu']=X_test_new_features['Medu']+X_test_new_features['Fedu']\n",
    "\n",
    "X_train_new_features=X_train_new_features.drop(['Medu','Fedu'],axis=1)\n",
    "X_test_new_features=X_test_new_features.drop(['Medu','Fedu'],axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Вспомним, что потребление алкоголя кореллирует с временем прогулок с друзьями, попробуем перемножить эти признаки.\n",
    "X_train_new_features['goout_alc']=X_train_new_features['goout']*X_train_new_features['Talc']\n",
    "X_test_new_features['goout_alc']=X_test_new_features['goout']*X_test_new_features['Talc']\n",
    "\n",
    "X_train_new_features=X_train_new_features.drop(['goout','Talc'],axis=1)\n",
    "X_test_new_features=X_test_new_features.drop(['goout','Talc'],axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Есть подозрение, что так как мужской пол негативно кореллирует с затратами времени на учебу, но мужской пол\n",
    "#положительно кореллирует с оценками по математике, то возможно\n",
    "#что если мальчик тратит больше времени на учебу, то его результат усиливается сильнее чем у девочек.\n",
    "X_train_new_features['studytime_eff']=X_train_new_features['studytime']*(X_train_new_features['sex_M']*0.5 + 1)\n",
    "X_test_new_features['studytime_eff']=X_test_new_features['studytime']*(X_test_new_features['sex_M']*0.5 + 1)\n",
    "\n",
    "X_train_new_features=X_train_new_features.drop(['studytime','sex_M'],axis=1)\n",
    "X_test_new_features=X_test_new_features.drop(['studytime','sex_M'],axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_new_features.T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Стало ли лучше? Снова проведем gridsearchCV.\n",
    "np.random.seed(42)\n",
    "forest_params_deep = {'n_estimators': [100,150,200,300,500], \n",
    "                 'max_depth': list(range(3, 13, 2)), \n",
    "                 'min_samples_leaf': list(range(5, 30, 5))}\n",
    "rfr=RandomForestRegressor(random_state=42)\n",
    "grid_rfr = GridSearchCV(rfr, forest_params_deep, \n",
    "                        cv=5,\n",
    "                        scoring='neg_mean_squared_error',\n",
    "                        n_jobs=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_rfr.fit(X_train_new_features, y_train_row)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#И результат снова немного улучшается\n",
    "print(-grid_rfr.best_score_,'\\n',grid_rfr.best_params_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Снова смотрим на важность признаков\n",
    "features = pd.DataFrame(grid_rfr.best_estimator_.feature_importances_, index=X_train_new_features.columns,\n",
    "                        columns=['Importance']).sort_values(['Importance'], ascending=False)\n",
    "features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Созданные признаки действительно оказались важны и улучшили качество модели###"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Построение кривых валидации и обучения ##"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_with_std(x, data, **kwargs):\n",
    "        mu, std = data.mean(1), data.std(1)\n",
    "        lines = plt.plot(x, mu, '-', **kwargs)\n",
    "        plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                         facecolor=lines[0].get_color(), alpha=0.2)\n",
    "        \n",
    "def plot_learning_curve(reg, X, y, scoring, cv=5):\n",
    " \n",
    "    train_sizes = np.linspace(0.05, 1, 20)\n",
    "    n_train, val_train, val_test = learning_curve(reg,\n",
    "                                                  X, y, train_sizes, cv=cv,\n",
    "                                                  scoring=scoring)\n",
    "    plot_with_std(n_train, val_train, label='training scores', c='green')\n",
    "    plot_with_std(n_train, val_test, label='validation scores', c='red')\n",
    "    plt.xlabel('Training Set Size'); plt.ylabel(scoring)\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Это график MSE со знаком минус, поэтому функции возрастают.\n",
    "plot_learning_curve(grid_rfr.best_estimator_,\n",
    "                   X_train_new_features, y_train_row, scoring='neg_mean_squared_error', cv=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Результаты на тестовой выборке ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Впринципе результаты сравнимы с результатами на кроссвалидации.\n",
    "#Хотя все же немного обидно, что различие существенно, ведь разбиение на трейн и тест было случайным.\n",
    "#Исходное приближение средним улучшить удалось.\n",
    "print(mean_squared_error(y_test_row,grid_rfr.best_estimator_.predict(X_test_new_features)),'vs baseline:',baseline_mean)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Выводы ##"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Нам удалось выделить наиболее значимые признаки влияющие на успеваемость школьников и это, наверное, самое главное, потому что на признаки можно повлиять. Также, зная успеваемость и признаки школьников из одного класса, можно спрогнозировать успеваемость в другом классе. Интересно было бы исследовать важность признаков для разных школ. Например, какие признаки наиболее влияют на успеваемость для сельской школы, а какие для городской. Для улучшения решения можно было бы дополнительно использовать градиентный бустинг, который себя неплохо показал, а потом его результаты объединить со случайным лесом. **"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
